#include "ICMPDef.h"
#include "util.c"

struct node* parse_probe_file(char* file_name) {
	int fd;	
	char *line, *port_p;
	struct node *node_list, *node;
	node_list = NULL;	

	fd = open(file_name, O_RDONLY);
	if (fd < 0) {
		printf("discovery.c: Error reading file %d, code %d\n", file_name, errno);
		return NULL;
	}

	line = malloc(sizeof(char) * 256);
	while (readline(fd, line, 256) > 0) {
		//ignore the new line char
		char *nl = strchr(line, '\n');
		if (nl != NULL) 	*nl = '\0';

		//create a new node
		node = malloc(sizeof(struct node));

		port_p = strchr(line, ':');
		if (port_p != NULL) {
			//printf("collon at %d\n", (port_p- line));
			*port_p = '\0';
			port_p++;
		} else {
			port_p = PROBE_DEFAULT_PORT;
		}
		node->port = atoi(port_p);
		node->addr = malloc(sizeof(char) * strlen(line));
		
		strcpy(node->addr, line);
		printf("discovery.c: configuring probe %s, port %d\n", node->addr, node->port);
		node_list = add_node(node_list, node);
	}
	free(line);
	return node_list;
}


void print_result_tree (struct cons_tree* final_tree, int depth) {
	if (final_tree != NULL) {
		char spaces[depth+1];
		memset(spaces, ' ', depth+1);
		spaces[depth+1] = '\0';

		printf("%s %s\n", spaces, final_tree->ip_addr);

		print_result_tree(final_tree->children, depth+1);
		print_result_tree(final_tree->siblings, depth);
	}
}

struct cons_tree* find_node(struct cons_tree* tree, char* ip_addr) {
	
	if (tree == NULL) {
		return NULL;
	}

	if (strcmp(tree->ip_addr, ip_addr) == 0) {
		print_debug("found!\n");
		return tree;
	}

	struct cons_tree* sibling = find_node(tree->siblings, ip_addr);
	if (sibling != NULL) {
		print_debug("looking in the siblings!\n");
		return sibling;
	}

	struct cons_tree* child = find_node(tree->children, ip_addr);
	if (child != NULL) {
		print_debug("looking in the children!\n");
		return child;
	}

	return NULL;
}

struct cons_tree* add_final_tree(struct cons_tree* final_tree, struct probe_result result, char* probe_tag) {
	struct cons_tree* new_tree = NULL;	
	//struct cons_tree* prev_node = NULL;
	int i;
	print_debug("will add node\n");

	// add probe
	struct cons_tree* prev_node = malloc(sizeof(struct cons_tree));
	bzero(prev_node , sizeof(struct cons_tree));
	strcpy(prev_node->ip_addr, probe_tag);
	prev_node->children = NULL;
	prev_node->siblings = NULL;

	for (i = 0; i < (result.num_hosts - 1); i++) {
		print_debug("looping thru results\n");
		
		struct cons_tree* node = find_node(final_tree, result.res[i].ip_addr);

		if (node == NULL) {
			print_debug("did not find the node, creating it...\n");
			struct cons_tree* node = malloc(sizeof(struct cons_tree));
			bzero(node, sizeof(struct cons_tree));

			strcpy(node->ip_addr, result.res[i].ip_addr);
			node->children = prev_node;
			node->siblings = NULL;

			print_debug("so far so good.\n");
			if ((new_tree == NULL) || (new_tree == prev_node)) {
				new_tree = node;
			}
			prev_node = node;
		}
		else {
			print_debug("found node \n");
			if (node->children == NULL) {
				print_debug("appending as child\n");
				node->children = prev_node;
			} else {
				print_debug("looking the siblings\n");
				struct cons_tree* sibling_node = node->children;
				while (sibling_node->siblings != NULL) {
					sibling_node = sibling_node->siblings;
				}
				print_debug("Adding as sibling\n");
				sibling_node->siblings = prev_node;
			}
			print_debug("ok,done\n");
			
			new_tree = final_tree;
			break;
		}
		print_debug ("next result\n");
	}

	return new_tree;
}


char* get_ip(char* host_address) {
	struct hostent *host;
	struct in_addr *addr;
	char *ip_addr, *temp;

	host = gethostbyname(host_address);

	if (host == NULL) {
		return NULL;
	}

	addr = (struct in_addr *) *host->h_addr_list;
	temp = (char *) inet_ntoa(addr->s_addr);

	ip_addr = malloc(sizeof(char) * IP_ADDR_SIZE);
	strncpy(ip_addr, temp, IP_ADDR_SIZE);	

	if (strcmp(ip_addr, host_address)) {
		printf("%s resolves to %s\n", host_address, ip_addr);	
	}

	return ip_addr;
}

int main(int argc, char *argv[]) {
	int fd, i;	
	struct sockaddr_in servaddr;
	struct probe_args args;
	struct probe_result result;
	struct node* node_list, *node_pt;
	struct cons_tree* final_tree = NULL;
	char *addr = argv[1];

	if (argc < 2) {
		printf("Usage: discovery <address>\n");
		return -1;
	}

	node_list = parse_probe_file(PROBE_FILE_NAME);
	addr = get_ip(argv[1]);
	strncpy(args.addr, addr, 16);
	args.addr[16] = '\0';

	node_pt = node_list;
	while (node_pt != NULL) {
		fd = socket(AF_INET, SOCK_STREAM, 0);
		node_pt->socket = fd;

		bzero(&servaddr, sizeof(servaddr));
		servaddr.sin_family = AF_INET;
		servaddr.sin_port = htons(node_pt->port);
		servaddr.sin_addr.s_addr = inet_addr(node_pt->addr);

		if (connect(fd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
			printf("discovery.c: Error connecting to probe %s:%d.\n", node_pt->addr, node_pt->port);
			node_pt = node_pt->next;
			continue;
		}

		printf("sending request to probe %s:%d\n", node_pt->addr, node_pt->port);
		writen(fd, &args, sizeof(args));

		// waiting result from probe
		readn(fd, &result, sizeof(result));

		if (result.error == TRUE) {
			printf("probe %s returned error tracing route to %s\n", node_pt->addr, addr);
		}
		else if (result.num_hosts == 0) {
			printf("no hosts returned\n");
		}
		else {
			if (DEBUG_MODE == TRUE) {
				printf("Path discovered by probe %s:%d\n", node_pt->addr, node_pt->port);
				for (i = 0; i < (result.num_hosts - 1); i++) {
					//char spaces[i+1];
					//memset(spaces, ' ', i+1);
					//spaces[i+1] = '\0';
					//printf("(%d): %s%s\n", (i+1), spaces, result.res[i].ip_addr);
					printf("(%d): %s\n", (i+1), result.res[i].ip_addr);
				}
			}
			char probe_tag[23];
			snprintf(probe_tag, 22, "%s:%d", node_pt->addr, node_pt->port);
			probe_tag[23] = '\0';
			final_tree = add_final_tree(final_tree, result, probe_tag);
		}
		close(fd);
		node_pt = node_pt->next;
	}

	printf("-------------- Result  -----------------\n");
	print_result_tree(final_tree, 0);

}
